#include <iostream>
#include <cstdlib>
#include <sys/time.h>
#include <iomanip>
#include <fas/mux/epoller.hpp>
#include <fas/inet/client.hpp>
#include <fas/adv/json_rpc3/method.hpp>

#include "hitlist/rn_json_aspect.hpp"
#include "hitlist/tags.hpp"
#include "hitlist/hit_json.hpp"
#include "hitlist/hit_counts_json.hpp"
#include "hitlist/hitlist_request_json.hpp"
#include "hitlist/hit_item_json.hpp"


#define MAX_USER 200000
#define MIDDLE_HITS 10
#define REQUEST_COUNT 100000

namespace aa = ::fas::aop;
namespace ai = ::fas::inet;
namespace am = ::fas::mux;
namespace ap = ::fas::pattern;
namespace ad = ::fas::adv;
namespace ajr = ::fas::adv::json_rpc3;

struct client_hit_method
{
  //ihitlist* hitlist;
  size_t count;
  
  client_hit_method():count(0) {}
  
  const char *name() const { return "hit"; }
  
  /*
  typedef hit_json invoke_notify;
  typedef hit_json invoke_request;
  typedef aj::value<bool> invoke_response;
  */

  /*
  typedef publish_json call_notify;
  */
  typedef hit_json call_request;
  typedef aj::value<bool> call_response;
  

  /*typedef publish_json::target request_type;
  typedef publish_json::target response_type;
  */

  template<typename T>
  void clear(T&) { }

  template<typename T>
  void response(T& t, bool result, int id )
  {
    ++count;
    /*hitlist->hit(h.first_id, h.second_id);
    result = true;
    */
    //return true;
  }
 
};

typedef aa::tag_list_n<
  aa::tag<_hit_>,
  aa::gtag< ajr::_gmethod_ >,
  aa::gtag< ad::_cleaner_ >
>::type client_hit_method_tag_list;

struct client_hit_method_advice
  : aa::advice<
      client_hit_method_tag_list,
      ajr::method<client_hit_method>
    >
{};

/// /////////////////////////

struct client_hit_counts_method
{
  //ihitlist* hitlist;
  size_t count;
  
  client_hit_counts_method():count(0) {}
  
  const char *name() const { return "hit_counts"; }
  
  /*
  typedef hit_json invoke_notify;
  typedef hit_json invoke_request;
  typedef aj::value<bool> invoke_response;
  */

  /*
  typedef publish_json call_notify;
  */
  
  typedef aj::value<user_id_t > call_request;
  typedef hit_counts_json call_response;

  /*typedef publish_json::target request_type;
  typedef publish_json::target response_type;
  */

  template<typename T>
  void clear(T&) { }

  template<typename T>
  void response(T& t, const hit_counts& hc, int id )
  {
    ++count;
//    std::cout << hc.hits_d << " " << hc.hits_m << " " << hc.hosts_d << " " << hc.hosts_m << std::endl;
    /*hitlist->hit(h.first_id, h.second_id);
    result = true;
    */
    //return true;
  }
 
};

typedef aa::tag_list_n<
  aa::tag<_hit_counts_>,
  aa::gtag< ajr::_gmethod_ >,
  aa::gtag< ad::_cleaner_ >
>::type client_hit_counts_method_tag_list;

struct client_hit_counts_method_advice
  : aa::advice<
      client_hit_counts_method_tag_list,
      ajr::method<client_hit_counts_method>
    >
{};

/// 

struct client_hitlist_method
{
  size_t count;
  
  client_hitlist_method(): count(0) {}
  
  const char *name() const { return "hitlist"; }
  
  typedef hitlist_request_json call_request;
  typedef aj::array<hit_item_json> call_response;
  //typedef hit_item_json aaa[50];
  //typedef aj::array<aaa> call_response;

  /*
  typedef hitlist_request_json invoke_notify;
  typedef hitlist_request_json invoke_request;
  typedef aj::array<hit_item_json> invoke_response;
  
  typedef aj::array<hit_item_json> call_notify;
  */
  
  
  /*
  typedef hit_json invoke_notify;
  typedef hit_json invoke_request;
  typedef aj::value<bool> invoke_response;
  */

  /*
  typedef publish_json call_notify;
  typedef message_json call_request;
  typedef message_json call_response;
  */

  /*typedef publish_json::target request_type;
  typedef publish_json::target response_type;
  */

  template<typename T>
  void clear(T&) { }

  /*
  template<typename T>
  void response(T& t, const hit_item hc[50], int id )
  {
    ++count;
  }*/

  
  template<typename T>
  void response(T& t, const std::vector<hit_item>& hc, int id )
  {
    //std::cout << hc.size() << std::endl;
    ++count;
  }

  /*
  template<typename T>
  bool request(T& t, const hitlist_request& req, int id, std::vector<hit_item>& res )
  {
    return true;
  }

  template<typename T>
  void notify(T& t, const hitlist_request& req )
  {
    std::vector<hit_item> res;
    t.get_aspect().template get<_hitlist_>().notify(t, res);
  }
  */
};

typedef aa::tag_list_n<
  aa::tag<_hitlist_>,
  aa::gtag< ajr::_gmethod_ >,
  aa::gtag< ad::_cleaner_ >
>::type client_hitlist_method_tag_list;

struct client_hitlist_method_advice
  : aa::advice<
      client_hitlist_method_tag_list,
      ajr::method<client_hitlist_method>
    >
{};



typedef ap::type_list_n<
  client_hit_method_advice,
  client_hit_counts_method_advice,
  client_hitlist_method_advice
>::type client_method_list;

typedef aa::aspect<client_method_list> client_method_aspect;

struct client_aspect: aa::aspect_merge< rn_json_aspect, client_method_aspect>::type {};

class client
  : public ai::mux_client< client_aspect >
{
  typedef ai::mux_client< client_aspect > super;
public:
  void reset()
  {
    super::get_aspect().get<_hit_>().count = 0;
    super::get_aspect().get<_hit_counts_>().count = 0;
    super::get_aspect().get<_hitlist_>().count = 0;
  }
  size_t hit_count() { return super::get_aspect().get<_hit_>().count; }
  size_t hit_counts_count() { return super::get_aspect().get<_hit_counts_>().count; }
  size_t hitlist_count() { return super::get_aspect().get<_hitlist_>().count; }
  
};


void test_hit(am::imux<>* mux, client& cli, size_t max_id, size_t count)
{
  cli.reset();
  hit h;
  h.hit_time = 0;
  for (size_t  i =0 ; i < count; ++i)
  {
    h.first_id = rand()%max_id;
    h.second_id = rand()%max_id;
    cli.get_aspect().get<_hit_>().request(cli, h);
    mux->select(200);
  }
  
  while ( cli.hit_count() != count )
    mux->select(-1);
};

void test_hit_counts(am::imux<>* mux, client& cli, size_t max_id, size_t count)
{
  cli.reset();
  hit h;
  h.hit_time = 0;
  for (size_t  i =0 ; i < count; ++i)
  {
    cli.get_aspect().get<_hit_counts_>().request(cli, rand()%max_id );
    mux->select(200);
  }
  
  while ( cli.hit_counts_count() != count )
    mux->select(-1);
};

void test_hitlist(am::imux<>* mux, client& cli, size_t max_id, size_t count)
{
  cli.reset();
  hitlist_request req;
  req.type = hitlist_request::hits_m;
  req.offset = 0;
  req.limit = 50;
  
  for (size_t  i =0 ; i < count; ++i)
  {
    req.first_id = rand()%max_id;
    cli.get_aspect().get<_hitlist_>().request(cli, req );
    mux->select(200);
  }
  
  while ( cli.hitlist_count() != count )
    mux->select(-1);
};



timeval calcspan( timeval start, timeval finish )
{
  timeval result;
  result.tv_sec = finish.tv_sec - start.tv_sec;
  result.tv_usec = 0;
  if ( finish.tv_usec < start.tv_usec )
  {
    result.tv_sec-=1;
    result.tv_usec = 1000000 + finish.tv_usec - start.tv_usec ;
  }
  else
    result.tv_usec = finish.tv_usec - start.tv_usec;
  return result;
}


int main(int argc, const char* argv[])
{
  std::string addr;
  //size_t middle_hits = argc < 3 ? MIDDLE_HITS : std::atoi(argv[2]);
  if ( argc > 1 ) addr = argv[1];
  unsigned short port = argc > 2 ? std::atoi(argv[2]) : 12345;
  size_t max_users = argc > 3 ? std::atoi(argv[3]) : MAX_USER;
  size_t popular_user = max_users / 100;
  size_t star_user = max_users / 1000;

  am::epoller mux;
  client cli;
  cli.set_nonblock(true);
  cli.set_mux(&mux);
  
  if ( addr.empty() )
    addr = ::fas::system::inet::gethostip();
  std::cout << "подключение " << addr << " " << port  << std::endl;
  cli.connect(addr, port);
  std::cout << "connect ready " << std::endl;
  
  std::cout << "хиты для случайных юзверей" << std::endl;
  timeval start, finish;
  ::gettimeofday(&start, 0);
  test_hit(&mux, cli, max_users, REQUEST_COUNT );
  ::gettimeofday(&finish, 0);
  timeval span = calcspan( start, finish );
  std::cout << "готово " << std::endl;
  std::cout << "Число запросов: " << REQUEST_COUNT << std::endl;
  std::cout << "Время: "<< span.tv_sec << "." << std::setw(6) << std::setfill('0') << span.tv_usec << " сек"<< std::endl;
  double rate = ( REQUEST_COUNT * 1000) / ( span.tv_sec * 1000 + span.tv_usec / 1000 ) ;
  std::cout << "Скорость "<< rate << " запросов в секунду" << std::endl;

  std::cout << "хиты для популярных юзверей" << std::endl;
  ::gettimeofday(&start, 0);
  test_hit(&mux, cli, popular_user, REQUEST_COUNT );
  ::gettimeofday(&finish, 0);
  span = calcspan( start, finish );
  std::cout << "готово " << std::endl;
  std::cout << "Число запросов: " << REQUEST_COUNT << std::endl;
  std::cout << "Время: "<< span.tv_sec << "." << std::setw(6) << std::setfill('0') << span.tv_usec << " сек"<< std::endl;
  rate = ( REQUEST_COUNT * 1000) / ( span.tv_sec * 1000 + span.tv_usec / 1000 ) ;
  std::cout << "Скорость "<< rate << " запросов в секунду" << std::endl;

  std::cout << "хиты для очень популярных юзверей" << std::endl;
  ::gettimeofday(&start, 0);
  test_hit(&mux, cli, star_user, REQUEST_COUNT );
  ::gettimeofday(&finish, 0);
  span = calcspan( start, finish );
  std::cout << "готово " << std::endl;
  std::cout << "Число запросов: " << REQUEST_COUNT << std::endl;
  std::cout << "Время: "<< span.tv_sec << "." << std::setw(6) << std::setfill('0') << span.tv_usec << " сек"<< std::endl;
  rate = ( REQUEST_COUNT * 1000) / ( span.tv_sec * 1000 + span.tv_usec / 1000 ) ;
  std::cout << "Скорость "<< rate << " запросов в секунду" << std::endl;

  ///

  std::cout << "Счетчики для случайных юзверей" << std::endl;
  ::gettimeofday(&start, 0);
  test_hit_counts(&mux, cli, max_users, REQUEST_COUNT );
  ::gettimeofday(&finish, 0);
  span = calcspan( start, finish );
  std::cout << "готово " << std::endl;
  std::cout << "Число запросов: " << REQUEST_COUNT << std::endl;
  std::cout << "Время: "<< span.tv_sec << "." << std::setw(6) << std::setfill('0') << span.tv_usec << " сек"<< std::endl;
  rate = ( REQUEST_COUNT * 1000) / ( span.tv_sec * 1000 + span.tv_usec / 1000 ) ;
  std::cout << "Скорость "<< rate << " запросов в секунду" << std::endl;

  
  std::cout << "Счетчики для популярных юзверей" << std::endl;
  ::gettimeofday(&start, 0);
  test_hit_counts(&mux, cli, popular_user, REQUEST_COUNT );
  ::gettimeofday(&finish, 0);
  span = calcspan( start, finish );
  std::cout << "готово " << std::endl;
  std::cout << "Число запросов: " << REQUEST_COUNT << std::endl;
  std::cout << "Время: "<< span.tv_sec << "." << std::setw(6) << std::setfill('0') << span.tv_usec << " сек"<< std::endl;
  rate = ( REQUEST_COUNT * 1000) / ( span.tv_sec * 1000 + span.tv_usec / 1000 ) ;
  std::cout << "Скорость "<< rate << " запросов в секунду" << std::endl;

  
  std::cout << "Счетчики для очень популярных юзверей" << std::endl;
  ::gettimeofday(&start, 0);
  test_hit_counts(&mux, cli, star_user, REQUEST_COUNT );
  ::gettimeofday(&finish, 0);
  span = calcspan( start, finish );
  std::cout << "готово " << std::endl;
  std::cout << "Число запросов: " << REQUEST_COUNT << std::endl;
  std::cout << "Время: "<< span.tv_sec << "." << std::setw(6) << std::setfill('0') << span.tv_usec << " сек"<< std::endl;
  rate = ( REQUEST_COUNT * 1000) / ( span.tv_sec * 1000 + span.tv_usec / 1000 ) ;
  std::cout << "Скорость "<< rate << " запросов в секунду. " << std::endl;

  ///

  std::cout << "Запрос списков для случайных юзверей" << std::endl;
  ::gettimeofday(&start, 0);
  test_hitlist(&mux, cli, max_users, REQUEST_COUNT );
  ::gettimeofday(&finish, 0);
  span = calcspan( start, finish );
  std::cout << "готово " << std::endl;
  std::cout << "Число запросов: " << REQUEST_COUNT << std::endl;
  std::cout << "Время: "<< span.tv_sec << "." << std::setw(6) << std::setfill('0') << span.tv_usec << " сек"<< std::endl;
  rate = ( REQUEST_COUNT * 1000) / ( span.tv_sec * 1000 + span.tv_usec / 1000 ) ;
  std::cout << "Скорость "<< rate << " запросов в секунду" << std::endl;

  ///

  std::cout << "Запрос списков для популярных юзверей" << std::endl;
  ::gettimeofday(&start, 0);
  test_hitlist(&mux, cli, popular_user, REQUEST_COUNT );
  ::gettimeofday(&finish, 0);
  span = calcspan( start, finish );
  std::cout << "готово " << std::endl;
  std::cout << "Число запросов: " << REQUEST_COUNT << std::endl;
  std::cout << "Время: "<< span.tv_sec << "." << std::setw(6) << std::setfill('0') << span.tv_usec << " сек"<< std::endl;
  rate = ( REQUEST_COUNT * 1000) / ( span.tv_sec * 1000 + span.tv_usec / 1000 ) ;
  std::cout << "Скорость "<< rate << " запросов в секунду" << std::endl;

  ///
  std::cout << "Запрос списков для очень популярных юзверей" << std::endl;
  ::gettimeofday(&start, 0);
  test_hitlist(&mux, cli, star_user, REQUEST_COUNT );
  ::gettimeofday(&finish, 0);
  span = calcspan( start, finish );
  std::cout << "готово " << std::endl;
  std::cout << "Число запросов: " << REQUEST_COUNT << std::endl;
  std::cout << "Время: "<< span.tv_sec << "." << std::setw(6) << std::setfill('0') << span.tv_usec << " сек"<< std::endl;
  rate = ( REQUEST_COUNT * 1000) / ( span.tv_sec * 1000 + span.tv_usec / 1000 ) ;
  std::cout << "Скорость "<< rate << " запросов в секунду" << std::endl;

  /*for (;;)
    mux.select(-1);
    */

  /*
  hitlist hl;
  hitlist_backup hb;
  hl.backup(&hb);
  backup bj;
  backup bb;
  hb.binary_backup(&bb);
  hb.json_backup(&bj);
  bb.initialize("./backup.test.binary", "backup_%d.binary");
  bj.initialize("./backup.test.json", "backup_%d.json");


  std::cout << "Тестирование логики hitlist на производительность" << std::endl;
  std::cout << "Соответственно быстродействие демона не может быть выше указанных значений" << std::endl;
  std::cout << std::endl;

  size_t popular_user = max_users / 100;
  size_t star_user = max_users / 1000;
  size_t user_per_day = max_users / 10;

  size_t popular_hits = middle_hits * 10;
  size_t star_hits = middle_hits * 100;

  std::cout << "Всего пользователей: " <<  max_users << std::endl;
  std::cout << "Пользователей ежедневно: " <<  user_per_day << ", которые делают по " << middle_hits << " ежедневно." << std::endl;
  std::cout << "Популярных пользователей: " <<  popular_user << ", которых смотрят по " << popular_hits << " ежедневно." << std::endl;
  std::cout << "Очень популярных пользователей: " <<  star_user << ", которых смотрят по " << star_hits << " ежедневно." << std::endl;
  std::cout << "нажми кнопку для начала инициализации" << std::endl;
  std::cin.get();
  // user_hits(&hl, 123, 10000, 1000, 0);
  for ( int i = 30 ; i != 0; --i)
  {
    timeval start, finish;
    std::cout <<std::endl << "day -"<<i<<"..."<<std::endl;

    ::gettimeofday(&start, 0);
    size_t result = day_hits(&hl, time(0) - 3600*24*i, user_per_day, max_users, popular_user, star_user, middle_hits);
    ::gettimeofday(&finish, 0);

    std::cout << "Запросов: " << result << ", всего хитов: "<<hl.size()<<", уникальных: " << hl.hosts_size() << std::endl;
    timeval span = calcspan( start, finish );
    std::cout << "Время "<< span.tv_sec << "." << std::setw(6) << std::setfill('0') << span.tv_usec << " сек"<< std::endl;
    double rate = (result * 1000000) / ( span.tv_sec * 1000000 + span.tv_usec ) ;
    std::cout << "Скорость "<< rate << " хитов в секунду" << std::endl;
  }
  
  std::cout << "и еще раз нажми кнопку " << std::endl;
  std::cin.get();
  for (size_t i = 0 ; i < 10; i++)
    std::cout << i << "\t" << hl.hits_month(i) << "\t" << hl.hosts_month(i) << "\t" << hl.hits_day(i) << "\t" << hl.hosts_day(i) << std::endl;

  std::cout << "и еще раз нажми кнопку " << std::endl;
  std::cin.get();
  for (size_t i = star_user ; i < star_user + 10; i++)
    std::cout << i << "\t" << hl.hits_month(i) << "\t" << hl.hosts_month(i) << "\t" << hl.hits_day(i) << "\t" << hl.hosts_day(i)<< std::endl;

  std::cout << "и еще раз нажми кнопку " << std::endl;
  std::cin.get();
  for (size_t i = popular_user ; i < popular_user + 10; i++)
    std::cout << i << "\t" << hl.hits_month(i) << "\t" << hl.hosts_month(i) << "\t" << hl.hits_day(i) << "\t" << hl.hosts_day(i)<< std::endl;
  
  /// ///////////////////////////////////////
  /// ///////////////////////////////////////
  /// запускаем сервер
  am::epoller mux;
  service s;
  s.initialize(&mux, &hl);
  s.start("", 12345);
  for (;;)
  {
    mux.select(-1);
    hb.flush();
  }
  */

  return 0;
}
